package castledefense.logic.units;

import java.util.ArrayList;

import castledefense.data.BuildingData;
import castledefense.data.GameData;
import castledefense.data.UnitData;
import castledefense.logic.Dot;
import castledefense.logic.Map;
import castledefense.logic.Selectable;
import castledefense.logic.Targetable;
import castledefense.logic.buildings.Castle;

public class Unit extends Mover implements Selectable, Targetable {

	private boolean attackCastle;
	protected double damage = 1;
	protected double initialHealth = 10;
	protected double health = initialHealth;
	protected int timeBetweenAttack = 2 * 1000;
	protected int timeBeforeNextAttack = timeBetweenAttack;
	private boolean isLife = true;
	private boolean isSelected = false;
	protected double regenerationPercent = 0;

	// debuffs
	private boolean isStunned = false;
	private int stunTime = 0;

	private double slow = 0;
	private int slowTime = 0;

	public Unit(int x, int y, int type, Map map) {
		this(x, y, 0.5, UnitData.getSpeed(type), type, map);
	}

	public Unit(int x, int y, double size, double speed, int type, Map map) {
		super(x, y, size, speed, type, map);
		damage = UnitData.getDamage(type);
		health = UnitData.getHealth(type);
		initialHealth = health;
		timeBetweenAttack = UnitData.getTimeBetweenAttack(type);
		timeBeforeNextAttack = timeBetweenAttack;
		regenerationPercent = UnitData.getRegenerationPercent(type);		
	}

	public void update(int deltaTime) {
		if (health < initialHealth) {
			health += health * regenerationPercent * deltaTime;
		}
		slowTime -= deltaTime;
		if (slowTime < 0) {
			slow = 0;
		}

		dotTime -= deltaTime;

		Dot removable = null;
		for (Dot dot : dotList) {
			if (dot.update(deltaTime)) {
				removable = dot;
			}
		}
		if (removable != null) {
			dotList.remove(removable);
		}

		if (!isStunned) {
			attackCastle(deltaTime);
			updatePath(deltaTime);
		} else {
			stunTime -= deltaTime;
			if (stunTime <= 0) {
				isStunned = false;
			}
		}	

	}

	public boolean isLife() {
		return isLife;
	}

	public boolean isDot() {
		return isLife && dotTime > 0;
	}

	public boolean isStun() {
		return isLife && isStunned;
	}

	public boolean isSlow() {
		return isLife && slow != 0;
	}

	public int getStunTime() {

		return isLife ? stunTime : 0;
	}

	public int getSlowTime() {
		return isLife ? slowTime : 0;
	}

	public int getDotDuration() {
		return isLife ? dotTime : 0;
	}

	public double getInitialHealth() {
		return initialHealth;
	}

	public double getDamage() {
		return damage;
	}

	public double getHealth() {
		return health;
	}

	@Override
	public double getSpeed() {
		return speed * (1 - slow);
	}

	public int getType() {
		return getMoverType();
	}

	public boolean isSelected() {
		return isSelected;
	}

	public void setSelected(boolean value) {
		isSelected = value;
	}

	public float getRenderingX() {
		return (float) (getMinX() * Map.cellWidth);
	}

	public float getRenderingY() {
		return (float) (getMinY() * Map.cellWidth);
	}

	public float getRenderingCenterX() {
		return (float) (getCenterX() * Map.cellWidth);
	}

	public float getRenderingCenterY() {
		return (float) (getCenterY() * Map.cellWidth);
	}

	public float getRenderingSize() {
		return (float) (getSize() * Map.cellWidth);
	}

	public void killUnit() {
		health = 0;
		isLife = false;
	}

	public boolean biggerContains(double x, double y) {
		if ((getMinX() - getSize() <= x && getMaxX() + getSize() >= x)
				&& (getMinY() - getSize() <= y && getMaxY() + getSize() >= y)) {
			return true;
		}
		return false;
	}

	public boolean isContains(int x, int y, int size) {
		int ux = (int) getCenterX();
		int uy = (int) getCenterY();
		if (x <= ux && x + size > ux && y <= uy && y + size > uy) {
			return true;
		}
		return false;
	}

	public boolean decreaseHealth(double damage, int element) {
		int effect = GameData.getElementEffect(getType(), element);

		if (element == GameData.noElement) {
			health -= damage;
		} else if (effect == GameData.resistanceElement) {
			health -= 0.75 * damage;
		} else if (effect == GameData.oppositeElement) {
			health -= 1.25 * damage;
		} else if (effect == GameData.noEffectElement) {
			health -= damage;
		} else {
			health -= damage;
		}

		if (health <= 0) {
			final boolean tmp = isLife;
			map.removeUnit(this);
			BuildingData.money++;
			killUnit();
			if (tmp)
				return true;
		}
		return false;
	}

	private void attackCastle(int deltaTime) {
		if (!attackCastle) {
			Castle castle = map.getCastle();
			if (castle.contains(getCenterX(), getCenterY())) {
				stopMoving();
				attackCastle = true;
			}
		} else {
			timeBeforeNextAttack -= deltaTime;
			if (timeBeforeNextAttack < 0) {
				timeBeforeNextAttack = timeBetweenAttack;				
				Castle castle = map.getCastle();
				castle.damage(damage);
			}

		}
	}

	public void stun(int time) {
		if (time > stunTime) {
			stunTime = time;
			isStunned = true;
		}
	}

	public boolean stunChance(int time, double chance) {
		if (chance > Math.random()) {
			stun(time);
			return true;
		}
		return false;
	}

	/** time as milliSeconds, amount as percents */
	public void slow(int time, double amount) {
		if (time > slowTime) {
			slowTime = time;
			slow = amount;
		}

	}

	private int dotTime = 0;

	private ArrayList<Dot> dotList = new ArrayList<Dot>();

	/** time and howOften as milliSeconds, */
	public void applyDot(int time, int howOften, double damagePerTick,
			int damageType) {

		dotTime = time;
		for (Dot tmp : dotList) {
			if (tmp.getTime() > dotTime) {
				dotTime = tmp.getTime();
			}
		}

		dotList.add(new Dot(time, howOften, damagePerTick, damageType, this));
	}

}
